package cn.wenhaha.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.wenhaha.common.bean.PluginInfo;
import cn.wenhaha.common.config.Global;
import org.pf4j.*;
import org.pf4j.util.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * 插件工具类
 * --------
 *
 * @author ：wyndem
 * @Date ：Created in 2023-01-24 16:35
 */
public class PluginUtils {

    private JarPluginManager manager;

    private PluginUtils(JarPluginManager manager) {
        this.manager = manager;
    }

    /**
     * 正式运行
     **/
    private static PluginUtils pluginManager;


    /**
     * <p>
     * 获取存放数据源插件的路径
     * </p>
     *
     * @Author: Wyndem
     * @DateTime: 2023-01-24 18:37
     */
    public static String getDataSourcePluginPath() {
        return Global.getRootPath() + File.separator + Global.getDataLib();
    }

    /**
     * <p>
     * 获取存放同步核心的插件路径
     * </p>
     *
     * @Author: Wyndem
     * @DateTime: 2023-01-25 12:14
     */
    public static String getSyncPluginPath() {
        return Global.getRootPath() + File.separator + Global.getCoreLib();
    }

    public synchronized static PluginUtils getPluginManager() {
        if (pluginManager == null) {
            pluginManager = newInstance();
        }
        return pluginManager;
    }


    public synchronized static PluginUtils newInstance(Path... path) {
        JarPluginManager defaultPluginManager = new JarPluginManager(path);
        return new PluginUtils(defaultPluginManager);
    }


    /**
     * <p>
     * 获取插件中的信息
     * </p>
     *
     * @Author: Wyndem
     * @DateTime: 2023-01-24 18:12
     */
    public Map<String, Object> getAttr(String path) {
        Path patho = Paths.get(path);

        if (!FileUtils.isJarFile(patho)) {
            throw new PluginRuntimeException("该文件不是一个标准的jar包 {}", path);
        }

        try (JarFile jar = new JarFile(patho.toFile())) {
            Manifest manifest = jar.getManifest();
            Attributes mainAttributes = manifest.getMainAttributes();
            Map<String, Object> map = new HashMap<>(mainAttributes.size());
            mainAttributes.forEach((k, v) -> {
                if (k instanceof Attributes.Name) {
                    Attributes.Name name = (Attributes.Name) k;
                    map.put(name.toString(), v);
                }
            });
            return map;
        } catch (IOException e) {
            throw new PluginRuntimeException(e, "Cannot read manifest from {}", path);
        }
    }


    /**
     * <p>
     * 获取基本插件中的信息
     * </p>
     *
     * @Author: Wyndem
     * @DateTime: 2023-01-24 18:12
     */
    public PluginInfo info(String path) {
        Map<String, Object> attributes = getAttr(path);
        if (!attributes.containsKey("Plugin-Id")) {
            return null;
        }
        return PluginInfo.builder()
                .id(MapUtil.getStr(attributes, "Plugin-Id"))
                .version(MapUtil.getStr(attributes, "Plugin-Version"))
                .info(MapUtil.getStr(attributes, "Plugin-Description"))
                .author(MapUtil.getStr(attributes, "Plugin-Provider"))
                .name(MapUtil.getStr(attributes, "Plugin-Name"))
                .icon(MapUtil.getStr(attributes, "Plugin-Icon"))
                .attr(attributes)
                .build();

    }


    /**
     * <p>
     *    检查是否可以停止该插件
     * </p>
     * @Author: Wyndem
     * @DateTime: 2023-01-26 18:41
     */
    public  synchronized void checkStop(String id){
        manager.checkStop(id);
    }

    public PluginState status(String id) {
        PluginWrapper plugin = manager.getPlugin(id);
        if (plugin == null) {
            return PluginState.DISABLED;
        }
        return plugin.getPluginState();
    }

    public synchronized void update(String path, String id) {
        if (!manager.deletePlugin(id)) {
            throw new PluginRuntimeException("删除原插件失败，本次更新失败");
        }
        load(path);
    }


    public synchronized String load(String path) {
        return manager.loadPlugin(Paths.get(path));
    }

    public synchronized PluginState start(String id) {
        return manager.startPlugin(id);
    }

    public synchronized String loadAndStart(String path) {
        String id = load(path);
        manager.startPlugin(id);
        return id;
    }

    public synchronized void unload(String id) {
        if (containPluginId(id)) {
            manager.stopPlugin(id);
            manager.unloadPlugin(id);
        }

    }


    public synchronized boolean delete(String id) {
        // 如果本身就不存在该id，则认为已经删除成功
        if (!containPluginId(id)) {
            return true;
        }
        manager.stopPlugin(id);
        return manager.deletePlugin(id);
    }


    public boolean containPluginId(String id) {
        return manager.getPlugin(id) != null;
    }

    public <T> boolean contain(Class<T> clazz, String id) {
        return getObject(clazz, id) != null;
    }


    /**
     * <p>
     * 获取对象实例
     * </p>
     *
     * @Author: Wyndem
     * @DateTime: 2023-01-24 17:10
     */
    public <T> T getObject(Class<T> clazz, String id) {
        List<T> extensions = manager.getExtensions(clazz, id);
        if (extensions.isEmpty()) {
            return null;
        }
        return extensions.get(0);
    }


    public PluginManager getManager() {
        return manager;
    }


    private static class JarPluginManager extends org.pf4j.JarPluginManager {

        public JarPluginManager() {
        }

        public JarPluginManager(Path... pluginsRoots) {
            super(pluginsRoots);
        }


        /**
         * <p>
         * 获取依赖本插件下的所有id
         * </p>
         *
         * @Author: Wyndem
         * @DateTime: 2023-01-26 18:31
         */
        public List<String> getDependents(String pluginId) {
            return dependencyResolver.getDependents(pluginId);
        }

        /**
         * <p>
         * 获取本插件下需要依赖的所有插件id
         * </p>
         *
         * @Author: Wyndem
         * @DateTime: 2023-01-26 18:35
         */
        public List<String> getDependencies(String pluginId) {
            return dependencyResolver.getDependencies(pluginId);
        }

        /**
         * <p>
         *    检查是否可以关闭插件
         * </p>
         * @Author: Wyndem
         * @DateTime: 2023-01-26 18:39
         */
        public void checkStop(String pluginId){
            // 查看谁依赖了本插件
            List<String> dependents = getDependents(pluginId);
            for (String dependent : dependents) {
                // 如果该插件不存在管理器中，则刷新缓存
                if (!plugins.containsKey(dependent)) {
                    resolvePlugins();
                    dependents = getDependents(pluginId);
                    break;
                }
            }

            // 检查如果存在该依赖，状态是否已关闭
            for (String dependent : dependents) {
                PluginWrapper pluginWrapper = getPlugin(dependent);
                PluginState pluginState = pluginWrapper.getPluginState();
                if (pluginState == PluginState.STARTED || pluginState == PluginState.RESOLVED || pluginState == PluginState.CREATED) {
                    throw new RuntimeException("请先关闭依赖插件 :" + pluginWrapper.getPluginId());
                }
            }
        }


        /**
         * <p>
         * 单例
         * </p>
         *
         * @Author: Wyndem
         * @DateTime: 2023-01-25 14:05
         */
        @Override
        protected ExtensionFactory createExtensionFactory() {
            return new SingletonExtensionFactory(this);
        }

        @Override
        public boolean unloadPlugin(String pluginId) {
            List<String> dependents = getDependents(pluginId);
            List<String> dependencies = getDependencies(pluginId);
            boolean b = super.unloadPlugin(pluginId);
            // 如果该插件id存在依赖关系，则刷新依赖关系
            if (b && (CollUtil.isNotEmpty(dependencies) || CollUtil.isNotEmpty(dependents))) {
                resolvePlugins();
            }
            return b;
        }

        /**
         * <p>
         * 停止之前，检查是否还存在依赖关系
         * </p>
         *
         * @Author: Wyndem
         * @DateTime: 2023-01-25 14:05
         */
        @Override
        public PluginState stopPlugin(String pluginId) {
            checkStop(pluginId);
            return super.stopPlugin(pluginId);
        }

    }

}
